﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Configuration;

namespace Cars
{
    public enum CarsMainFormState
    { 
        Stopped,
        Waiting,
        EditingFilters,
        Crawling,
        CrawlingStopped,
        Closing
    }

    public partial class CarsMainForm : Form
    {
        private List<CarFilter> _filters = null;
        private List<CarInfo> _cars = null;
        private BackgroundWorker _carWorker = null;
        private BackgroundWorker _notifyWorker = null;
        private CarsMainFormState _state = CarsMainFormState.Stopped;
        private string _statusText;

        //private string _lastAdId = "";

        private int _crawlInterval;
        private string _smtpServer;
        private int _smtpPort;
        private string _smtpUsername;
        private string _smtpPassword;
        private string[] _notifyEmail;
        private string[] _notifySms;
        private string _fromEmail;

        public CarsMainForm()
        {
            InitializeComponent();

            // make sure we're in stopped state
            State = CarsMainFormState.Stopped;

            _cars = new List<CarInfo>();

            _carWorker = new BackgroundWorker();
            _carWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
            _carWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_carWorker_RunWorkerCompleted);

            _notifyWorker = new BackgroundWorker();
            _notifyWorker.DoWork += new DoWorkEventHandler(_notifyWorker_DoWork);

            // load the filter file
            LoadFilter();
            LoadSettings();

            timer1.Interval = _crawlInterval;
        }                

        private void LoadSettings()
        {
            _crawlInterval = Convert.ToInt32(ConfigurationSettings.AppSettings["CrawlInterval"]) * 1000;
            _smtpServer = Convert.ToString(ConfigurationSettings.AppSettings["SMTPServer"]);
            _smtpPort = Convert.ToInt32(ConfigurationSettings.AppSettings["SMTPPort"]);
            _smtpUsername = Convert.ToString(ConfigurationSettings.AppSettings["SMTPUsername"]);
            _smtpPassword = Convert.ToString(ConfigurationSettings.AppSettings["SMTPPassword"]);
            _notifyEmail = Convert.ToString(ConfigurationSettings.AppSettings["NotifyEmail"]).Split(',');
            _notifySms = Convert.ToString(ConfigurationSettings.AppSettings["NotifySms"]).Split(',');
            _fromEmail = Convert.ToString(ConfigurationSettings.AppSettings["FromEmail"]);
        }

        private void LoadFilter()
        {
            _filters = null;
                       
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(List<CarFilter>));
            System.IO.StreamReader reader = new System.IO.StreamReader("CarFilter.xml");
            _filters = x.Deserialize(reader) as List<CarFilter>;          
        }

        public CarsMainFormState State
        {
            get
            {
                return _state;
            }
            set
            {
                if (_state == value)
                    return;

                switch (_state)
                {
                    case CarsMainFormState.Stopped:
                        if ((value != CarsMainFormState.Waiting) && 
                            (value != CarsMainFormState.Closing))
                            throw new Exception(string.Format("Invalid state change. {0} {1}", _state, value));
                        break;
                    case CarsMainFormState.Waiting:
                        if ((value != CarsMainFormState.Stopped) &&
                            (value != CarsMainFormState.Closing) &&
                            (value != CarsMainFormState.Crawling))
                            throw new Exception(string.Format("Invalid state change. {0} {1}", _state, value));
                        break;
                    case CarsMainFormState.EditingFilters:
                        if ((value != CarsMainFormState.Stopped) &&
                            (value != CarsMainFormState.Waiting))
                            throw new Exception(string.Format("Invalid state change. {0} {1}", _state, value));
                        break;
                    case CarsMainFormState.Crawling:
                        if ((value != CarsMainFormState.CrawlingStopped) &&
                            (value != CarsMainFormState.Waiting))
                            throw new Exception(string.Format("Invalid state change. {0} {1}", _state, value));
                        break;
                    case CarsMainFormState.CrawlingStopped:
                        if (value != CarsMainFormState.Stopped)
                            throw new Exception(string.Format("Invalid state change. {0} {1}", _state, value));
                        break;
                    default:
                        throw new Exception(string.Format("Invalid state change. {0} {1}", _state, value));
                }

                _state = value;

                switch (_state)
                {
                    case CarsMainFormState.Stopped:
                        btnStartStop.Text = "&Start";
                        StatusText = "Stopped...";
                        btnStartStop.Enabled = true;
                        btnEditFilters.Enabled = true;
                        timer1.Enabled = false;
                        break;
                    case CarsMainFormState.Waiting:
                        btnStartStop.Text = "&Stop";
                        StatusText = "Waiting...";
                        btnStartStop.Enabled = true;
                        btnEditFilters.Enabled = true;
                        timer1.Enabled = true;
                        break;
                    case CarsMainFormState.EditingFilters:
                        StatusText = "Editing Filters...";
                        btnStartStop.Enabled = false;
                        btnEditFilters.Enabled = false;
                        timer1.Enabled = false;
                        break;
                    case CarsMainFormState.Crawling:
                        btnStartStop.Text = "&Stop";
                        StatusText = "Crawling...";
                        btnStartStop.Enabled = true;
                        btnEditFilters.Enabled = false;
                        timer1.Enabled = false;
                        break;
                    case CarsMainFormState.CrawlingStopped:
                        btnStartStop.Text = "Stopping...";
                        StatusText = "Stopping...";
                        btnStartStop.Enabled = false;
                        btnEditFilters.Enabled = false;
                        timer1.Enabled = false;
                        break;
                    case CarsMainFormState.Closing:
                        btnStartStop.Text = "Closing...";
                        StatusText = "Closing...";
                        btnStartStop.Enabled = false;
                        btnEditFilters.Enabled = false;
                        timer1.Enabled = false;
                        break;
                    default:
                        throw new Exception(string.Format("Invalid state. {0}", _state));
                }                
            }
        }

        public string StatusText
        {
            get 
            {
                return _statusText;
            }
            set 
            {
                _statusText = value;
                notifyIcon1.Text = "Cars - " + value;
                notifyIcon1.BalloonTipText = notifyIcon1.Text;
                Text = notifyIcon1.Text;
            }
        }

        private void btnEditFilters_Click(object sender, EventArgs e)
        {            
            using (EditCarFilter filterForm = new EditCarFilter())
            {
                timer1.Enabled = false;
                try
                {
                    filterForm.Data = _filters;

                    if (filterForm.ShowDialog() == DialogResult.OK)
                    {
                        System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(_filters.GetType());
                        System.IO.StreamWriter writer = new System.IO.StreamWriter("CarFilter.xml");
                        x.Serialize(writer, _filters);
                    }
                    else
                    {
                        LoadFilter();
                    }
                }
                finally
                {
                    timer1.Enabled = true;
                }
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            State = CarsMainFormState.Crawling;
            _carWorker.RunWorkerAsync();
        }

        void _carWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (State == CarsMainFormState.CrawlingStopped)
                State = CarsMainFormState.Stopped;
            else
                State = CarsMainFormState.Waiting;
        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            KslCrawler crawler = new KslCrawler();
            crawler.OnCarFound += new CarFoundEvent(worker_CarFound);
            string _lastAdId = _cars.Count == 0 ? "" : _cars[_cars.Count - 1].AdId;
            crawler.Crawl(_lastAdId, 0, 9, _filters);
        }

        // this can be called from either the worker thread or the main thread
        // so if called from the worker, use invoke to call it from the main
        // thread.
        private void worker_CarFound(CarInfo car, ref bool cancel)
        {
            // InvokeRequired required compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (this.lbxAlerts.InvokeRequired)
            {          
                CarFoundEvent d = new CarFoundEvent(worker_CarFound);
                this.Invoke(d, new object[] { car, cancel });
            }
            else
            {
                // make sure we don't notify for the same car again
                if (_cars.Find(delegate (CarInfo tmp) {return tmp.AdId == car.AdId;}) != null)
                {
                    cancel = true;
                    return;
                }

                // if the user has cancelled, this is a good time to stop
                if (State == CarsMainFormState.CrawlingStopped)
                {
                    cancel = true;
                    return;
                }

                string title = string.Format("{0} {1} {2} {3:C}", car.Year, car.Make, car.Model, car.Price);
                string body = string.Format("http://www.ksl.com/index.php?sid=&nid=443&tab=list/view&ad={0} \r\n {1}", car.AdId, car.Description);

                string shortMessage = string.Format("{0} {1} {2} {3:C}", car.Year, car.Make, car.Model, car.Price);

                lbxAlerts.Items.Add(shortMessage);

                // send sms one at a time
                foreach (string s in _notifySms)
                {
                    WebUtils.SmtpSend(new string[1] { s }, _fromEmail, "KSL Alert", title, _smtpServer, _smtpPort, _smtpUsername, _smtpPassword);
                }

                // send email all at the same time
                WebUtils.SmtpSend(_notifyEmail, _fromEmail, title, body, _smtpServer, _smtpPort, _smtpUsername, _smtpPassword);

                _cars.Add(car);

                // show the notify form
                _notifyWorker.RunWorkerAsync(car);

                // block until the form is hidden
                while (_notifyWorker.IsBusy)
                    Application.DoEvents();
            }            
        }

        void _notifyWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            CarsNotifyForm notifyForm = new CarsNotifyForm();
            notifyForm.Car = (e.Argument as CarInfo);
            notifyForm.ShowDialog();
        }

        private void btnStartStop_Click(object sender, EventArgs e)
        {
            if (State == CarsMainFormState.Stopped)
            {
                State = CarsMainFormState.Waiting;
            }
            else if (State == CarsMainFormState.Crawling)
            {
                State = CarsMainFormState.CrawlingStopped;
            }
            else
            {
                State = CarsMainFormState.Stopped;
            }            
        }

        //private void Start()
        //{
        //    btnStartStop.Text = "Stop";
        //    timer1.Enabled = true;
        //    notifyIcon1.BalloonTipText = "Cars - Running";
        //    notifyIcon1.Text = notifyIcon1.BalloonTipText;            
        //}

        //private void Stop()
        //{
        //    btnStartStop.Text = "Start";
        //    timer1.Enabled = false;
        //    notifyIcon1.BalloonTipText = "Cars - Waiting";
        //    notifyIcon1.Text = notifyIcon1.BalloonTipText;            
        //}

        private void showToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Visible = true;
            if (this.WindowState == FormWindowState.Minimized)
                this.WindowState = FormWindowState.Normal;
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            State = CarsMainFormState.Stopped;
            Application.Exit();
        }

        private void CarsMainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                Hide();
                e.Cancel = true;
            }
        }

        private void CarsMainForm_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                notifyIcon1.Visible = true;
                this.Visible = false;
                this.ShowInTaskbar = false;
            }
        }

        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            showToolStripMenuItem_Click(sender, e);
        }
        
    }
}
